/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:          weapons.inc
 *  Type:          Core 
 *  Description:   API for all weapon-related functions.
 *
 *  Copyright (C) 2009  Greyscale, Richard Helgeby
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ============================================================================
 */

/**
 * Maximum length of a weapon name string
 */
#define WEAPONS_MAX_LENGTH 32

/**
 * Number of REAL weapon slots (For CS:S)
 */
#define WEAPONS_SLOTS_MAX 5

/**
 * @section CS:S start weapons.
 */
#define WEAPONS_SPAWN_T_WEAPON "weapon_glock"
#define WEAPONS_SPAWN_CT_WEAPON "weapon_usp"
/**
 * @endsection
 */

/**
 * Weapon config data indexes.
 */
enum WeaponsData
{
    WEAPONS_DATA_NAME = 0,
    WEAPONS_DATA_ENTITY,
    WEAPONS_DATA_TYPE,
    WEAPONS_DATA_SLOT,
    WEAPONS_DATA_RESTRICTDEFAULT,
    WEAPONS_DATA_TOGGLEABLE,
    WEAPONS_DATA_AMMOTYPE,
    WEAPONS_DATA_AMMOPRICE,
    WEAPONS_DATA_KNOCKBACK,
    WEAPONS_DATA_ZMARKETPRICE,
    WEAPONS_DATA_ZMARKETPURCHASEMAX,
    WEAPONS_DATA_RESTRICTED,
}

/**
 * @endsection
 */

/**
 * Variable to store active weapon offset value.
 */
new g_iToolsActiveWeapon;

/**
 * Weapon slots.
 */
enum WeaponsSlot
{
    Slot_Invalid        = -1,   /** Invalid weapon (slot). */
    Slot_Primary        = 0,    /** Primary weapon slot. */
    Slot_Secondary      = 1,    /** Secondary weapon slot. */
    Slot_Melee          = 2,    /** Melee (knife) weapon slot. */
    Slot_Projectile     = 3,    /** Projectile (grenades, flashbangs, etc) weapon slot. */
    Slot_Explosive      = 4,    /** Explosive (c4) weapon slot. */
    Slot_NVGs           = 5,    /** NVGs (fake) equipment slot. */
}

/**
 * Array handle to store weapon config data.
 */
new Handle:arrayWeapons = INVALID_HANDLE;

#include "zr/weapons/restrict"
#include "zr/weapons/weaponammo"
#include "zr/weapons/weaponalpha"
#include "zr/weapons/zmarket"
#include "zr/weapons/menu_weapons"

/**
 * Weapons module init function.
 */
WeaponsInit()
{
    // Forward event to sub-modules.
    RestrictInit();
}

/**
 * All plugins have finished loading.
 */
WeaponsOnAllPluginsLoaded()
{
    // Forward event to sub-modules.
    WeaponAmmoOnAllPluginsLoaded();
}

/**
 * Find active weapon-specific offsets here.
 */
WeaponsOnOffsetsFound()
{
    // If offset "m_hActiveWeapon" can't be found, then stop the plugin.
    g_iToolsActiveWeapon = FindSendPropInfo("CBasePlayer", "m_hActiveWeapon");
    if (g_iToolsActiveWeapon == -1)
    {
        LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Weapons, "Offsets", "Offset \"CBasePlayer::m_hActiveWeapon\" was not found.");
    }
    
    // If offset "m_bInBuyZone" can't be found, then stop the plugin.
    g_iToolsInBuyZone = FindSendPropInfo("CCSPlayer", "m_bInBuyZone");
    if (g_iToolsInBuyZone == -1)
    {
        LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Weapons, "Offsets", "Offset \"CCSPlayer::m_bInBuyZone\" was not found.");
    }
    
    // Forward event to sub-modules
    WeaponAmmoOnOffsetsFound();
}

/**
 * Create commands related to weapons here.
 */
WeaponsOnCommandsCreate()
{
    // Forward event to sub-modules.
    RestrictOnCommandsCreate();
    ZMarketOnCommandsCreate();
}

/**
 * Create weapon-related cookies here.
 */
WeaponsOnCookiesCreate()
{
    // Forward event to sub-modules.
    ZMarketOnCookiesCreate();
}

/**
 * Loads weapon data from file.
 */
WeaponsLoad()
{
    // Register config file.
    ConfigRegisterConfig(File_Weapons, Structure_Keyvalue, CONFIG_FILE_ALIAS_WEAPONS);
    
    // If module is disabled, then stop.
    new bool:weapons = GetConVarBool(g_hCvarsList[CVAR_WEAPONS]);
    if (!weapons)
    {
        return;
    }
    
    // Get weapons config path.
    decl String:pathweapons[PLATFORM_MAX_PATH];
    new bool:exists = ConfigGetCvarFilePath(CVAR_CONFIG_PATH_WEAPONS, pathweapons);
    
    // If file doesn't exist, then log and stop.
    if (!exists)
    {
        // Log failure.
        LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Weapons, "Config Validation", "Missing weapons config file: %s", pathweapons);
        
        return;
    }
    
    // Set the path to the config file.
    ConfigSetConfigPath(File_Weapons, pathweapons);
    
    // Load config from file and create array structure.
    new bool:success = ConfigLoadConfig(File_Weapons, arrayWeapons);
    
    // Unexpected error, stop plugin.
    if (!success)
    {
        LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Weapons, "Config Validation", "Unexpected error encountered loading: %s", pathweapons);
    }
    
    // Validate weapons config.
    new size = GetArraySize(arrayWeapons);
    if (!size)
    {
        LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Weapons, "Config Validation", "No usable data found in weapons config file: %s", pathweapons);
    }
    
    // Now copy data to array structure.
    WeaponsCacheData();
    
    // Set config data.
    ConfigSetConfigLoaded(File_Weapons, true);
    ConfigSetConfigReloadFunc(File_Weapons, GetFunctionByName(GetMyHandle(), "WeaponsOnConfigReload"));
    ConfigSetConfigHandle(File_Weapons, arrayWeapons);
    
    // Forward event to sub-modules
    RestrictLoad();
}

/**
 * Caches weapon data from file into arrays.
 * Make sure the file is loaded before (ConfigLoadConfig) to prep array structure.
 */
WeaponsCacheData()
{
    // Get config's file path.
    decl String:pathweapons[PLATFORM_MAX_PATH];
    ConfigGetConfigPath(File_Weapons, pathweapons, sizeof(pathweapons));
    
    new Handle:kvWeapons;
    new bool:success = ConfigOpenConfigFile(File_Weapons, kvWeapons);
    
    if (!success)
    {
        LogEvent(false, LogType_Fatal, LOG_CORE_EVENTS, LogModule_Weapons, "Config Validation", "Unexpected error caching data from weapons config file: %s", pathweapons);
    }
    
    decl String:weaponname[WEAPONS_MAX_LENGTH];
    
    // x = array index
    new size = GetArraySize(arrayWeapons);
    for (new x = 0; x < size; x++)
    {
        WeaponsGetName(x, weaponname, sizeof(weaponname));
        KvRewind(kvWeapons);
        if (!KvJumpToKey(kvWeapons, weaponname))
        {
            LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Weapons, "Config Validation", "Couldn't cache weapon data for: %s (check weapons config)", weaponname);
            continue;
        }
        
        // Get config data.
        
        decl String:weaponentity[CONFIG_MAX_LENGTH];
        decl String:weapontype[CONFIG_MAX_LENGTH];
        decl String:ammotype[CONFIG_MAX_LENGTH];
        
        // General
        KvGetString(kvWeapons, "weaponentity", weaponentity, sizeof(weaponentity));
        KvGetString(kvWeapons, "weapontype", weapontype, sizeof(weapontype));
        new WeaponsSlot:weaponslot = WeaponsSlot:KvGetNum(kvWeapons, "weaponslot", -1);
        
        // Restrict (core)
        new bool:restrictdefault = ConfigKvGetStringBool(kvWeapons, "restrictdefault", "no");
        new bool:toggleable = ConfigKvGetStringBool(kvWeapons, "toggleable", "yes");
        
        // Weapon Ammo (core)
    	KvGetString(kvWeapons, "ammotype", ammotype, sizeof(ammotype));
    	new ammoprice = KvGetNum(kvWeapons, "ammoprice", -1);
    	
        // Knockback (module)
        new Float:knockback = KvGetFloat(kvWeapons, "knockback", 1.0);
        
        // ZMarket (module)
        new zmarketprice = KvGetNum(kvWeapons, "zmarketprice", -1);
        new zmarketpurchasemax = KvGetNum(kvWeapons, "zmarketpurchasemax", 0);
        
        new Handle:arrayWeapon = GetArrayCell(arrayWeapons, x);
        
        // Push data into array.
        PushArrayString(arrayWeapon, weaponentity);         // Index: 1
        PushArrayString(arrayWeapon, weapontype);           // Index: 2
        PushArrayCell(arrayWeapon, weaponslot);             // Index: 3
        PushArrayCell(arrayWeapon, restrictdefault);        // Index: 4
        PushArrayCell(arrayWeapon, toggleable);             // Index: 5
        PushArrayString(arrayWeapon, ammotype);             // Index: 6
        PushArrayCell(arrayWeapon, ammoprice);              // Index: 7
        PushArrayCell(arrayWeapon, knockback);              // Index: 8
        PushArrayCell(arrayWeapon, zmarketprice);           // Index: 9
        PushArrayCell(arrayWeapon, zmarketpurchasemax);    // Index: 10
        
        // Initialize other stored weapon info here.
        PushArrayCell(arrayWeapon, restrictdefault);        // Index: 11
    }
    
    // We're done with this file now, so we can close it.
    CloseHandle(kvWeapons);
}

/**
 * Called when config is being reloaded.
 */
public WeaponsOnConfigReload()
{
    // Reload weapons config.
    WeaponsLoad();
}
                    
/**
 * Client is joining the server.
 * 
 * @param client    The client index.  
 */
WeaponsClientInit(client)
{
    // Forward event to sub-modules.
    RestrictClientInit(client);
    WeaponAlphaClientInit(client);
    ZMarketClientInit(client);
}

/**
 * Called once a client's saved cookies have been loaded from the database.
 * 
 * @param client		Client index.
 */
WeaponsOnCookiesCached(client)
{
    // Forward event to sub-modules.
    ZMarketOnCookiesCached(client);
}

/**
 * Client is leaving the server.
 * 
 * @param client    The client index.
 */
WeaponsOnClientDisconnect(client)
{
    // Forward event to sub-modules.
    RestrictOnClientDisconnect(client);
    WeaponAlphaOnClientDisconnect(client);
    ZMarketOnClientDisconnect(client);
}

/**
 * Client is spawning into the game.
 * 
 * @param client    The client index.
 */
WeaponsOnClientSpawn(client)
{
    // Forward event to sub-modules.
    RestrictOnClientSpawn(client);
}

/**
 * Client is spawning into the game. *Post
 * 
 * @param client    The client index.
 */
WeaponsOnClientSpawnPost(client)
{
    // Refresh all weapons on clients to cleanup any rendering errors.
    WeaponsRefreshAllClientWeapons(client);
    
    // Forward event to sub-modules.
    ZMarketOnClientSpawnPost(client);
}

/**
 * The round is ending.
 */
WeaponsOnRoundEnd()
{
    // Forward event to sub-modules.
    RestrictOnRoundEnd();
}

/**
 * Called when a client picks up an item.
 * 
 * @param client    The client index.
 * @param weapon    The weapon index.
 */
WeaponsOnItemPickup(client, weapon)
{
    // Forward event to sub-modules.
    
    // Fire post OnItemPickup event.
    
    // Fill datapack with event information.
    new Handle:eventinfo = CreateDataPack();
    WritePackCell(eventinfo, client);
    WritePackCell(eventinfo, weapon);
    
    // Create post delay timer.
    CreateTimer(0.0, WeaponsOnItemPickupPost, eventinfo, TIMER_DATA_HNDL_CLOSE);
}

/**
 * Called when a client picks up an item. *Post
 * 
 * @param client    The client index.
 * @param weapon    The weapon index.
 */
public Action:WeaponsOnItemPickupPost(Handle:timer, Handle:eventinfo)
{
    // Get event info.
    ResetPack(eventinfo);
    new client = ReadPackCell(eventinfo);
    new weapon = ReadPackCell(eventinfo);
    
    // If client isn't in the game anymore, then stop.
    if (!IsClientInGame(client))
    {
        return;
    }
    
    // If the weapon entity isn't valid anymore, then stop.
    if (!IsValidEdict(weapon))
    {
        return;
    }
    
    // Forward event to sub-modules.
    WeaponAlphaOnItemPickupPost(client, weapon);
}

/**
 * Weapon data reading API.
 */

/**
 * Clear cache for a given weapon.
 * 
 * @param index     The weapon index.
 */
stock WeaponsClearCache(index)
{
    // Get array handle of weapon at given index.
    new Handle:hWeapon = GetArrayCell(arrayWeapons, index);
    
    // Clear array.
    ClearArray(hWeapon);
}

/**
 * Find the index at which the weapon's name is at.
 * 
 * @param weapon    The weapon name.
 * @return          The array index containing the given weapon name.
 */
stock WeaponsNameToIndex(const String:weapon[])
{
    decl String:weaponname[WEAPONS_MAX_LENGTH];
    
    // x = Array index.
    new size = GetArraySize(arrayWeapons);
    for (new x = 0; x < size; x++)
    {
        WeaponsGetName(x, weaponname, sizeof(weaponname));
        
        // If names match, then return index.
        if (StrEqual(weapon, weaponname, false))
        {
            return x;
        }
    }
    
    // Name doesn't exist.
    return -1;
}

/**
 * Takes a weapon's entity name and returns the display name in weapons config file.
 * 
 * @param entityname    The entity to find the display of.
 * @param display       Buffer to store display name in.
 * @param displaymaxlen The max length of the display name.
 * @param noprefix      If this is true, the entity name will be compared without the weapon_/item_ prefix.
 * @return              The index of the weapon found.  
 */
stock WeaponsEntityToDisplay(const String:entityname[], String:display[], displaymaxlen, noprefix = false)
{
    // Check if weapon data is loaded.
    if (arrayWeapons == INVALID_HANDLE)
    {
        return -1;
    }
    
    decl String:weaponentity[WEAPONS_MAX_LENGTH];
    
    // Initialize string to null.
    strcopy(display, sizeof(displaymaxlen), "");
    
    // x = Array index.
    new size = GetArraySize(arrayWeapons);
    for (new x = 0; x < size; x++)
    {
        // If entity names don't match, then stop.
        WeaponsGetEntity(x, weaponentity, sizeof(weaponentity));
        
        // If noprefix is true, then strip the weapon_/item_ prefix.
        if (noprefix)
        {
            ReplaceString(weaponentity, sizeof(weaponentity), "weapon_", "");
            ReplaceString(weaponentity, sizeof(weaponentity), "item_", "");
        }
        
        if (!StrEqual(entityname, weaponentity, false))
        {
            continue;
        }
        
        // The entity names match, so return display.
        WeaponsGetName(x, display, displaymaxlen);
        
        // Return the weapon index.
        return x;
    }
    
    // Nothing was found.
    return -1;
}

/**
 * Checks if a weapon is valid. (E.G. listed in weapons.txt)
 * @param weapon    The weapon name.
 * @return          Returns true if valid, false it not.
 */
stock bool:WeaponsIsWeaponValid(const String:weapon[])
{
    return (WeaponsNameToIndex(weapon) != -1);
}

/**
 * Gets the name of a weapon at a given index.
 * @param index     The weapon index.
 * @param weapon    The string to return name in.
 * @param maxlen    The max length of the string.
 */
stock WeaponsGetName(index, String:weapon[], maxlen)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Get weapon name.
    GetArrayString(arrayWeapon, _:WEAPONS_DATA_NAME, weapon, maxlen);
}

/**
 * Gets the entity of a weapon at a given index.
 * @param index     The weapon index.
 * @param type      The string to return entity in.
 * @param maxlen    The max length of the string.
 */
stock WeaponsGetEntity(index, String:type[], maxlen)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Get weapon type.
    GetArrayString(arrayWeapon, _:WEAPONS_DATA_ENTITY, type, maxlen);
}

/**
 * Gets the type of a weapon at a given index.
 * @param index     The weapon index.
 * @param type      The string to return type in.
 * @param maxlen    The max length of the string.
 */
stock WeaponsGetType(index, String:type[], maxlen)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Get weapon type.
    GetArrayString(arrayWeapon, _:WEAPONS_DATA_TYPE, type, maxlen);
}

/**
 * Gets the slot index of a weapon at a given index.
 * @param index     The weapon index.
 * @return          The slot index of the weapon.
 */
stock WeaponsSlot:WeaponsGetSlot(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return default restriction status.
    return WeaponsSlot:GetArrayCell(arrayWeapon, _:WEAPONS_DATA_SLOT);
}

/**
 * Gets if a weapon is restricted by default.
 * @param index     The weapon index.
 * @return          True if the weapon is restricted by default, false if not.
 */
stock bool:WeaponsGetRestrictDefault(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return default restriction status.
    return bool:GetArrayCell(arrayWeapon, _:WEAPONS_DATA_RESTRICTDEFAULT);
}

/**
 * Gets if a weapon's restriction status is toggleable.
 * @param index     The weapon index.
 * @return          True if the weapon restriction can be toggled, false if not.
 */
stock bool:WeaponsGetToggleable(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return if weapon is toggleable.
    return bool:GetArrayCell(arrayWeapon, _:WEAPONS_DATA_TOGGLEABLE);
}

/**
 * Gets the ammo type of a weapon at a given index.
 * @param index     The weapon index.
 * @param ammotype  The string to return ammotype in.
 * @param maxlen    The max length of the string.
 */
stock WeaponsGetAmmoType(index, String:ammotype[], maxlen)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Get ammo type of the weapon.
    GetArrayString(arrayWeapon, _:WEAPONS_DATA_AMMOTYPE, ammotype, maxlen);
}

/**
 * Gets the price of ammo for the weapon.
 * @param index     The weapon index.
 * @return          The ammo price.
 */
stock WeaponsGetAmmoPrice(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return ammo price of the weapon.
    return GetArrayCell(arrayWeapon, _:WEAPONS_DATA_AMMOPRICE);
}

/**
 * Gets the knockback multiplier for the weapon.
 * @param index     The weapon index.
 * @return          The weapon knockback multiplier.
 */
stock Float:WeaponsGetKnockback(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return knockback multiplier of the weapon.
    return Float:GetArrayCell(arrayWeapon, _:WEAPONS_DATA_KNOCKBACK);
}

/**
 * Gets the ZMarket price for the weapon.
 * @param index     The weapon index.
 * @return          The ZMarket price.
 */
stock WeaponsGetZMarketPrice(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return the ZMarket price of the weapon.
    return GetArrayCell(arrayWeapon, _:WEAPONS_DATA_ZMARKETPRICE);
}

/**
 * Gets the max purchases from ZMarket per round per client of a weapon.
 * @param index     The weapon index.
 * @return          The max purchases of the weapon.
 */
stock WeaponsGetZMarketPurchaseMax(index)
{
    // Get array handle of weapon at given index.
    new Handle:arrayWeapon = GetArrayCell(arrayWeapons, index);
    
    // Return the ZMarket price of the weapon.
    return GetArrayCell(arrayWeapon, _:WEAPONS_DATA_ZMARKETPURCHASEMAX);
}

/**
 * General weapon API.
 */

/**
 * Checks if a client has a specific weapon.
 * 
 * @param client    The client index.
 * @param weapon    The weapon classname.
 */
stock bool:WeaponsClientHasWeapon(client, const String:weapon[])
{
    // Get all of client's current weapons.
    new weapons[WeaponsSlot];
    WeaponsGetClientWeapons(client, weapons);
    
    decl String:classname[64];
    
    // x = slot index
    for (new x = 0; x < WEAPONS_SLOTS_MAX; x++)
    {
        // If slot is empty, then stop.
        if (weapons[x] == -1)
        {
            continue;
        }
        
        // If the weapon's classname matches, then return true.
        GetEdictClassname(weapons[x], classname, sizeof(classname));
        if (StrEqual(weapon, classname, false))
        {
            return true;
        }
    }
    
    return false;
}

/**
 * Return an array that contains all client's weapon indexes.
 * 
 * @param client    The client index.
 * @param weapons   The weapon index array.
 *                  -1 if no weapon in slot. 
 */
stock WeaponsGetClientWeapons(client, weapons[WeaponsSlot])
{
    // x = Weapon slot.
    for (new x = 0; x < WEAPONS_SLOTS_MAX; x++)
    {
        weapons[x] = GetPlayerWeaponSlot(client, x);
    }
}

/**
 * Returns weapon index of the client's deployed weapon.
 * 
 * @param client    The client index.
 * @return          The weapon index of the deployed weapon.
 *                  -1 if no weapon is deployed. 
 */
stock WeaponsGetDeployedWeaponIndex(client)
{
    // Return the client's active weapon.
    return GetEntDataEnt2(client, offsActiveWeapon);
}

/**
 * Returns slot of client's deployed weapon.
 *
 * @param client    The client index.
 * @return          The slot number of deployed weapon.
 */
stock WeaponsSlot:WeaponsGetDeployedWeaponSlot(client)
{
    // Get all client's weapon indexes.
    new weapons[WeaponsSlot];
    WeaponsGetClientWeapons(client, weapons);
    
    // Get client's deployed weapon.
    new deployedweapon = WeaponsGetDeployedWeaponIndex(client);
    
    // If client has no deployed weapon, then stop.
    if (deployedweapon == -1)
    {
        return Type_Invalid;
    }
    
    // x = weapon slot.
    for (new x = 0; x < WEAPONS_SLOTS_MAX; x++)
    {
        if (weapons[x] == deployedweapon)
        {
            return WeaponsSlot:x;
        }
    }
    
    return Type_Invalid;
}

/**
 * Forces player to drop weapon index. (Simplified wrapper)
 * 
 * @param client    The client index.
 * @param weapon    The weapon index to force client to drop.
 */
stock WeaponsForceClientDrop(client, weapon)
{
    // Force client to drop weapon.
    CS_DropWeapon(client, weapon, true, false);
}

/**
 * Used to explicitly remove projectile weapons from a client.
 * 
 * @param client    The client index.
 * @param weaponsdrop   True to force drop on all weapons, false to strip. 
 */
stock WeaponsRemoveClientGrenades(client, bool:weaponsdrop)
{
    // This while-structure is a stupid sloppy annoying workaround to stop the "unintended assignment" warning.  I hate those.
    // While GetPlayerWeaponSlot returns a valid projectile, remove it and then test again.
    new grenade = GetPlayerWeaponSlot(client, _:Slot_Projectile);
    while (grenade != -1)
    {
        // Check if we drop or strip the grenade.
        if (weaponsdrop)
        {
            WeaponsForceClientDrop(client, grenade);
        }
        else
        {
            RemovePlayerItem(client, grenade);
            AcceptEntityInput(grenade, "Kill");
        }
        
        // Find next grenade.
        grenade = GetPlayerWeaponSlot(client, _:Slot_Projectile);
    }
}

/**
 * Refresh a weapon by taking it and giving it back.
 * 
 * @param client        The client index.
 * @param slot          The weapon slot to refresh. (see enum WeaponsSlot)
 */
stock WeaponsRefreshClientWeapon(client, WeaponsSlot:slot)
{
    new weaponindex = GetPlayerWeaponSlot(client, _:slot);
    
    // If weapon is invalid, then stop.
    if (weaponindex == -1)
    {
        return;
    }
    
    // Get the classname of the weapon to re-give.
    decl String:entityname[WEAPONS_MAX_LENGTH];
    GetEdictClassname(weaponindex, entityname, sizeof(entityname));
    
    // Refresh weapon.
    RemovePlayerItem(client, weaponindex);
    AcceptEntityInput(weaponindex, "Kill");
    GivePlayerItem(client, entityname);
}

/**
 * Remove all weapons, except knife, on a client, with options.
 * 
 * @param client        The client index.
 * @param weaponsdrop   True to force drop on all weapons, false to strip.
 */
stock WeaponsRemoveAllClientWeapons(client, bool:weaponsdrop)
{
    // Get a list of all client's weapon indexes.
    new weapons[WeaponsSlot];
    WeaponsGetClientWeapons(client, weapons);
    
    // Loop through array slots and force drop.
    // x = weapon slot.
    for (new x = 0; x < WEAPONS_SLOTS_MAX; x++)
    {
        // If weapon is invalid, then stop.
        if (weapons[x] == -1)
        {
            continue;
        }
        
        // If this is the knife slot, then strip it and stop.
        if (WeaponsSlot:x == Slot_Melee)
        {
            // Strip knife.
            RemovePlayerItem(client, weapons[x]);
            AcceptEntityInput(weapons[x], "Kill");
            continue;
        }
        
        if (weaponsdrop)
        {
            // Force client to drop weapon.
            WeaponsForceClientDrop(client, weapons[x]);
        }
        else
        {
            // Strip weapon.
            RemovePlayerItem(client, weapons[x]);
            AcceptEntityInput(weapons[x], "Kill");
        }
    }
    
    // Remove left-over projectiles.
    WeaponsRemoveClientGrenades(client, weaponsdrop);
    
    // Give zombie a new knife. (If you leave the old one there will be glitches with the knife positioning)
    GivePlayerItem(client, "weapon_knife");
}

/**
 * Refresh a weapon by taking it and giving it back.
 * 
 * @param client        The client index.
 */
stock WeaponsRefreshAllClientWeapons(client)
{
    // Get a list of all client's weapon indexes.
    new weapons[WeaponsSlot];
    WeaponsGetClientWeapons(client, weapons);
    
    // Loop through array slots and force drop.
    // x = weapon slot.
    for (new x = 0; x < WEAPONS_SLOTS_MAX; x++)
    {
        // If weapon is invalid, then stop.
        if (weapons[x] == -1)
        {
            continue;
        }
        
        WeaponsRefreshClientWeapon(client, WeaponsSlot:x);
    }
}

/**
 * Checks if a client is in a buyzone.
 * 
 * @param client    The client index.
 */
stock bool:WeaponsIsClientInBuyZone(client)
{
    // Return if client is in buyzone.
    return bool:GetEntData(client, g_iToolsInBuyZone);
}
